package com.bonc.excel.biz;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import com.bonc.excel.entity.ColDto;
import com.bonc.excel.utils.ListPageUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author wujing
 * @DESCRIPTION
 */
//@Component
@Slf4j
public abstract class BaseExcelService implements ExcelService {

    private static final String FIRTST_HEAD_STR = "处室\\分项";

    //缓存calss
    Map<String,Class> CLASS_CACHE_MAP =Maps.newConcurrentMap();

    //维护 CtClass得name
    Map<String,AtomicInteger> CODE_INCREASE_MAP = Maps.newConcurrentMap();

    //维护行列规则
    private static  Map<String,String> ROW_COL_CACHE_MAP = Maps.newConcurrentMap();

    private static final  String rules = "{rows:%s,cols:%s}";



    private static final String DYNAMIC_FIELD_NAME = "name";

    //private static  ClassPool parent = ClassPool.getDefault();
    private static final String setStr = "public void set%s(String %s){this.%s = %s;}";
    private static final String getStr = "public String get%s(){return this.%s;}";


    /**
     *
     * @param code
     * @param datas      表格数据
     * @param rows       行表头数据
     * @param colLists   列表头数据
     * @return
     * @throws Exception
     */
    public  List<Object> setData(String code/*,ClassPool classPool*/
            ,List<String> datas, List<String> rows, List<ColDto> colLists) throws Exception {
        //CtClass ctClass = classPool.get("com.bonc.excel.dto.response.RspVo");
        //避免内存溢出 每次使用新的 ClassPool 对象
        ClassPool classPool = new ClassPool(true);
        //  ClassPool child = new ClassPool(parent);
        classPool.insertClassPath("./classes");

        String classname = genClassName(code, rows.size(), colLists.size());

        CtClass ctClass = classPool.makeClass(classname);
       // CtClass ctClass = classPool.makeClass(code.concat("service"));
      //  CtClass ctClass = classPool.get(classname);
        //ConstPool constPool = ctClass.getClassFile().getConstPool();

        //列表头
        LinkedHashMap<String, List<String>> colTitleMaps = getColTitle(colLists);

        Boolean dataConfigFlag = Boolean.FALSE;
        if(colTitleMaps.size() * rows.size() != datas.size()){
            log.info("数据配置异常:code:{},col size:{},row size:{},datas size:{}",code,colTitleMaps.size(),rows.size(),datas.size());
            dataConfigFlag = Boolean.TRUE;
        }
        //动态字段
        List<String> dynamicFields = addDynamicField(colTitleMaps, classPool, ctClass/*, constPool*/);
        log.info("code:{},dynamicFields:{}",code,JSONObject.toJSONString(dynamicFields));

        Class aClass = getCacheClass(code, ctClass);
        //赋值
        List<Object> list = getObjects(rows,datas,aClass,colTitleMaps.size(),dynamicFields,dataConfigFlag);
        ctClass.detach();
        ctClass = null;
        //log.info("data:{}", JSONObject.toJSONString(list));
        return list;
    }

    private List<String>  addDynamicField(LinkedHashMap<String, List<String>> colTitleMaps,ClassPool classPool
            , CtClass ctClass/*, ConstPool constPool*/) throws Exception  {
        List<String> dynamicFields = Lists.newArrayList();
        for (int i = 0; i < colTitleMaps.size()+1; i++) {
            String fieldName = DYNAMIC_FIELD_NAME + i;
            dynamicFields.add(fieldName);
            List<String> titleNames = Lists.newArrayList();
            if(i==0){
                titleNames.add(FIRTST_HEAD_STR);
            }else{
                titleNames = colTitleMaps.get(String.valueOf(i));
            }
            //添加字段 方法
            addField(classPool, fieldName, ctClass/*, constPool*/,titleNames);
        }
        return dynamicFields;
    }

    private Class getCacheClass(String code, CtClass ctClass) throws CannotCompileException {
        Class aClass = CLASS_CACHE_MAP.get(code);
        if(aClass == null){
            //将修改后的CtClass加载至当前线程的上下文类加载器中，CtClass的toClass方法是通过调用本方法实现。
            // 需要注意的是一旦调用该方法，则无法继续修改已经被加载的class
            aClass = ctClass.toClass();
            CLASS_CACHE_MAP.put(code,aClass);
        }else{
            log.info("code:{},Class走缓存", code);
        }
        return aClass;
    }

    private String genClassName(String code, int rowsSize, int colSize) {
        String classname = code.concat("service");

        if(CODE_INCREASE_MAP.get(code) == null){
            AtomicInteger atomicInteger = new AtomicInteger(0);
            classname = classname.concat(String.valueOf(atomicInteger.get()));
            CODE_INCREASE_MAP.put(code,atomicInteger);

            ROW_COL_CACHE_MAP.put(code,String.format(rules,rowsSize,colSize));
        }else{

            String newRules = String.format(rules,rowsSize,colSize);
            //如果行列有变化 重新生成class,清缓存
            if(!newRules.equals(ROW_COL_CACHE_MAP.get(code))){
                //新规则
                ROW_COL_CACHE_MAP.put(code,newRules);

                AtomicInteger ato = CODE_INCREASE_MAP.get(code);
                ato.getAndIncrement();
                classname = classname.concat(String.valueOf(ato.get()));
                CODE_INCREASE_MAP.put(code,ato);

                //清楚class缓存
                CLASS_CACHE_MAP.remove(code);
            }else{
                classname =  classname.concat(String.valueOf(CODE_INCREASE_MAP.get(code).get()));
            }

        }
        return classname;
    }

    /**
     * 构建表头处理单元格是否合并
     * @param params 列表头数据以及格式
     * @return
     */
    public LinkedHashMap<String, List<String>> getColTitle(List<ColDto> params) {
        if(CollectionUtil.isEmpty(params)){
            return Maps.newLinkedHashMap();
        }
        //col全量数据
        Map<String, String> colMap = params.stream()
                .collect(Collectors.toMap(ColDto::getColLevel, ColDto::getColName,(k1, k2) -> k1));

        //拿到 1  2  3  4...对应得数据 获取需要单元格合并得数据
        Map<String, List<ColDto>> sortedMap = params.stream().collect(Collectors.groupingBy(v -> v.getColLevel().split("_")[0]));
        // long count = params.stream().map(p -> p.getColLevel().split("_")[0]).count();
        // key升序排序
        sortedMap = sortedMap.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        // 解决可能存在的键冲突问题，默认保留第一个值
                        (oldValue, newValue) -> oldValue,
                        // 提供一个新的TreeMap实例作为收集器，用于保持排序
                        () -> new TreeMap<>()
                ));
        LinkedHashMap<String, List<String>> linkedHashMap = Maps.newLinkedHashMap();
        //列数控制
        AtomicInteger colCount = new AtomicInteger(1);
        //遍历第一列 第二列 ....  key为从1开始得整数
        sortedMap.forEach((key,value)->{
            //处理第key列单元格(单元格合并问题)

            //只有一列 不用考虑合并
            if(value.size() == 1){
                List<String> tempList = Lists.newArrayList(value.get(0).getColName());
                //tempList.add(value.get(0).getColName());
                linkedHashMap.put(String.valueOf(colCount.get()),tempList);
                colCount.getAndIncrement();
                return;
            }
           /* if(value.size() == 2){
                // 1  1_1  直接拿到1_1
                Col col = value.stream().filter(v -> v.getColLevel().startsWith(key + "_")).findFirst().get();
                tempList.add(colMap.get(key));
                tempList.add(col.getColName());
               // linkedHashMap.put(key,tempList);
                linkedHashMap.put(String.valueOf(colCount.get()),tempList);
                colCount.getAndIncrement();
                return;
            }*/

            //以下考虑单元格合并问题  比如存在格式  1_1_1  1_1_2

            //level降序
            Collections.sort(value, (a, b) -> b.getColLevel().compareTo(a.getColLevel()));
            ColDto col = value.get(0);
            //获取_出现最长次数
            long longestCount = col.getColLevel().chars().filter(ch -> ch == '_').count();
            List<ColDto> longestList = value.stream().filter(cols -> cols.getColLevel().chars()
                    .filter(ch -> ch == '_').count() == longestCount).collect(Collectors.toList());
            //level升序
            Collections.sort(longestList, (a, b) -> a.getColLevel().compareTo(b.getColLevel()));
            // longestList有几个就有几个单元格需要合并
            longestList.forEach(tempCol -> {
                List<String> tempList = Lists.newArrayList();
                //  1_1_1  构建为 1   1_1   1_1_1
                List<String> cols = genCol(tempCol.getColLevel());
                cols.forEach(s -> { tempList.add(colMap.get(s));});
                linkedHashMap.put(String.valueOf(colCount.get()),tempList);
                colCount.getAndIncrement();
            });
        });
        return linkedHashMap;
    }


    /**
     * @param
     * @param rowHeads       行表头
     * @param datas          表格数据
     * @param pageSize
     * @param dynamicFields  动态字段名字
     * @param dataConfigFlag 数据配置问题
     * @return
     * @throws Exception
     */
    private  List<Object> getObjects(List<String> rowHeads, List<String> datas,
                                     Class aClass, Integer pageSize, List<String> dynamicFields, Boolean dataConfigFlag) throws Exception {

       // Class<?> aClass = ctClass.toClass();
        List<Object> list = new ArrayList<Object>();
        //赋值
        for (int i = 0; i < rowHeads.size(); i++) {
            Object obj = aClass.newInstance();
            ListPageUtil<String> listPageUtil = new ListPageUtil<String>(datas,i+1, pageSize);
            List<String> pagedList = listPageUtil.getPagedList();
            //赋值
            for (int k = 0; k < dynamicFields.size(); k++) {
                String fieldValue = "";
                if(k == 0){
                    fieldValue = rowHeads.get(i);
                }else{
                    if(dataConfigFlag.booleanValue()){
                        try {
                            fieldValue = pagedList.get(k-1);
                          } catch (Exception e) {
                            fieldValue = "-";
                          }

                    }else{
                        fieldValue = pagedList.get(k-1);
                    }

                }
                String fieldName = dynamicFields.get(k);
                setFieldValue(obj,fieldName,fieldValue);
            }
            list.add(obj);
        }
        return list;
    }


    



    /**
     *
     * @param param
     * @return
     *  //目标:  1_1_1  构建为 1   1_1   1_1_1
     */
    private List<String> genCol(String param) {
        List<String> cols = Lists.newArrayList();
        /*for (int i = 1; i <= param.length(); i++) {
            if(i%2 ==0){
                continue;
            }
            cols.add(param.substring(0, i));
        }*/
        String[] split = param.split("_");
        StringBuilder  sb = new StringBuilder();
        for (int i = 0; i < split.length; i++) {
            if(i == 0){
                sb.append(split[0]);
                cols.add(sb.toString());
                continue;
            }
            sb =  sb.append("_").append(split[i]);
            cols.add(sb.toString());
        }
        return cols;
    }



    private void addField(ClassPool classPool
            , String fieldName, CtClass ctClass, /*ConstPool constPool,*/ List<String> titleNames) throws CannotCompileException, NotFoundException {
        ConstPool constPool = ctClass.getClassFile().getConstPool();
        // 增加属性，这里仅仅是增加属性字段
        // String fieldType = fieldValue.getClass().getName (); // 返回值的类型
        CtField ctField = new CtField(classPool.get(String.class.getName()), fieldName, ctClass);
        ctField.setModifiers(Modifier.PRIVATE); // 设置字段的编码修饰符

        // 首先创建注解
        AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation annotation = new Annotation("com.alibaba.excel.annotation.ExcelProperty", constPool);

        //设置title名字,已经处理过合并单元格
        MemberValue[] memberValues = new MemberValue[titleNames.size()];
       /* StringMemberValue stringMemberValue1 = new StringMemberValue("模型（表）注册", constPool);
        StringMemberValue stringMemberValue2 = new StringMemberValue("模型"+i, constPool);
        memberValues = new MemberValue[]{stringMemberValue1, stringMemberValue2};*/
        for (int i = 0; i < titleNames.size(); i++) {
            memberValues[i] =  new StringMemberValue(titleNames.get(i), constPool);
        }

        ArrayMemberValue arrayMemberValue = new ArrayMemberValue(constPool);
        arrayMemberValue.setValue(memberValues);
        annotation.addMemberValue("value", arrayMemberValue);
        annotationsAttribute.addAnnotation(annotation);

        // 在属性上添加注解
        ctField.getFieldInfo().addAttribute(annotationsAttribute);

        // 将字段添加到class中
        ctClass.addField(ctField);

        // 添加方法
        String upperCasField = capitalizeFirstLetter(fieldName);
     /*   String setStr = "public void set%s(String %s){this.%s = %s;}";
        String getStr = "public String get%s(){return this.%s;}";*/
        ctClass.addMethod(CtNewMethod.make(String.format(setStr,upperCasField,fieldName,fieldName,fieldName), ctClass));
        ctClass.addMethod(CtNewMethod.make(String.format(getStr,upperCasField,fieldName), ctClass));

    }

    private  void setFieldValue(Object object, String fieldName, String val) throws Exception {
        //Object result = null;
        Field fu = object.getClass().getDeclaredField(fieldName); // 获取对象的属性域
        // 设置对象属性域的访问属性
        fu.setAccessible(true);
        // 设置对象属性域的属性值
       // ExcelProperty annotation = fu.getAnnotation(ExcelProperty.class);
        fu.set(object,val);
    }


    private static String capitalizeFirstLetter(String str) {
        if (str == null || str.length() == 0) {
            return str;
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }



}
